using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Shutterstock.Salesforce.Tools.SFPartner;
using System.Net;
using System.Threading;
using System.IO;
using Shutterstock.Salesforce.SSISDownload.Templates;
using Shutterstock.Salesforce.Tools;
using Amib.Threading;
namespace Shutterstock.Salesforce.SSISDownload
{
    public static class SForceServiceExtensionMethods
    {
        #region GetTableInfo
        /// <summary>
        /// Get the table information. 
        /// </summary>
        /// <param name="sObjectType"></param>
        /// <returns></returns>
        public static SFTableInfo GetTableInfo(this SforceService service, string sObjectType)
        {
            SFTableInfo tableInfo = new SFTableInfo();
            tableInfo.Init(service.describeSObject(sObjectType));
            return tableInfo;
        }
        #endregion



        /// <summary>
        /// Get a datatable of all data from the query. 
        /// </summary>
        /// <param name="queryString"></param>
        /// <param name="tableInfo"></param>
        /// <returns></returns>
        public static DataTable GetDataTable(this SforceService service, string queryString, SFTableInfo tableInfo)
        {
            
            DataTable dt = new DataTable();

            DateTime datetime_limit = new DateTime(2000, 1, 1);
            bool done = false;
            Debug.WriteLine(DateTime.Now.ToString());

            if (service.QueryOptionsValue == null)
            {
                service.QueryOptionsValue = new QueryOptions();
            }
            
            service.QueryOptionsValue.batchSize = 2000;
            service.QueryOptionsValue.batchSizeSpecified = true;
            
            

            QueryResult queryResult = service.query(queryString);
            Debug.WriteLine(DateTime.Now.ToString());
            while (!done && queryResult.size != 0)
            {
                foreach (sObject sobject in queryResult.records)
                {
                    if (dt.Columns.Count == 0)
                    {
                        Type type;
                        Array.ForEach(sobject.Any, xe =>
                        {
                            type = SFTypeConverter.ConvertToType(tableInfo.GetColumnType(xe.LocalName));
                            dt.Columns.Add(xe.LocalName, type);
                        });

                    }
                    DataRow dr = dt.NewRow();
                    foreach (XmlElement xe in sobject.Any)
                    {
                        if (!xe.IsEmpty)
                        {
                            switch (tableInfo.GetColumnType(xe.LocalName))
                            {
                                case fieldType.base64:
                                    dr[xe.LocalName] = Convert.FromBase64String(xe.InnerText);
                                    break;
                                case fieldType.currency:
                                case fieldType.@double:
                                    dr[xe.LocalName] = Decimal.Parse(xe.InnerText, System.Globalization.NumberStyles.Any);
                                    break;
                                case fieldType.@int:
                                    dr[xe.LocalName] = Int32.Parse(xe.InnerText, System.Globalization.NumberStyles.Any);
                                    break;
                                case fieldType.boolean:
                                    bool parsedBool = Boolean.Parse(xe.InnerText);
                                    if (parsedBool)
                                    {
                                        dr[xe.LocalName] = 1;
                                    }
                                    else
                                    {
                                        dr[xe.LocalName] = 0;
                                    }
                                    break;
                                case fieldType.date:
                                case fieldType.datetime:
                                    DateTime parsed = DateTime.Parse(xe.InnerText);
                                    if (parsed < datetime_limit)
                                    {
                                        dr[xe.LocalName] = datetime_limit;
                                    }
                                    else
                                    {
                                        dr[xe.LocalName] = parsed;
                                    }

                                    break;
                                default:
                                    dr[xe.LocalName] = xe.InnerText;
                                    break;
                            }
                        }
                    }
                    dt.Rows.Add(dr);
                }
                if (queryResult.done)
                {
                    done = true;
                }
                else
                {
                    queryResult = service.queryMore(queryResult.queryLocator);
                }
            }
            return dt;
        }


        /// <summary>
        /// Get a list of queryable tables. 
        /// </summary>
        /// <returns></returns>
        public static List<string> GetQueryableTables(this SforceService service)
        {
            try
            {
                DescribeGlobalResult globalResult = service.describeGlobal();
                List<string> tables = new List<string>();
                Array.ForEach(globalResult.sobjects, obj =>
                {
                    if (obj.queryable)
                        tables.Add(obj.name);
                });
                //Array.ForEach(globalResult.types, obj => tables.Add(obj));
                return tables;
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Error while getting the list of tables. Message: {0} StackTrace: {1}", ex.Message, ex.StackTrace));
            }
            //return tables;
        }


        /// <summary>
        /// Get Deleted object ids
        /// </summary>
        /// <param name="tabeleName"></param>
        /// <param name="startDate"></param>
        /// <param name="endDate"></param>
        /// <returns></returns>
        public static List<string> GetDeletedIDs(this SforceService service, string tabeleName, DateTime startDate, DateTime endDate)
        {
            List<string> deletedRecordIDs = new List<string>();
            GetDeletedResult deletedResult = service.getDeleted(tabeleName, startDate, endDate);
            if (deletedResult.deletedRecords == null)
                return deletedRecordIDs;
            Array.ForEach(deletedResult.deletedRecords, dr => deletedRecordIDs.Add(dr.id));
            return deletedRecordIDs;
        }














        //////////////////////////////////////////////////////////////////////////////////////








        /// <summary>
        /// Get the URI Base of the Bulk Instance
        /// </summary>
        internal static string GetUri_Base(this ISalesForceConnection connection)
        {
            return String.Format("https://{0}/services/async/25.0/", connection.SFInstance);
        }

        /// <summary>
        /// Execute a Blocking Bulk query and save results to disk
        /// </summary>
        /// <param name="SFObjectName">Object to query</param>
        /// <param name="mode">the ConcurrencyMode (Serial/Parallel)</param>
        /// <param name="query">The query to execute</param>
        /// <param name="sleepTime">How long to sleep between polls</param>
        /// <param name="resultPath">Where to save the results</param>
        internal static void ExecuteBlockingQuery(this ISalesForceConnection connection, string SFObjectName, ConcurrencyMode mode, string query, int sleepTime, string resultPath)
        {
            string job_id = connection.CreateJob(SFObjectName, mode);
            string batchId = connection.BeginQuery(query, job_id);
            connection.GetResults(job_id, batchId, sleepTime, resultPath);
            connection.CloseJob(job_id);
        }

        /// <summary>
        /// Create a Bulk Query Job
        /// </summary>
        /// <param name="SFObjectName">Object to query</param>
        /// <param name="mode">the ConcurrencyMode (Serial/Parallel)</param>
        /// <returns>The Job Id</returns>
        internal static string CreateJob(this ISalesForceConnection connection, string SFObjectName, ConcurrencyMode mode)
        {
            string createJobTextbase = Resources.CreateJob;
            string createJobText = String.Format(createJobTextbase, SFObjectName, mode.ToString());
            string url = connection.GetUri_Base() + "job";


            System.Text.UTF8Encoding encoding = new UTF8Encoding();
            Byte[] payload = encoding.GetBytes(createJobText);

            WebRequest request = WebRequest.Create(url);
            request.Headers.Add("X-SFDC-Session", connection.SFSession);

            request.ContentType = "application/xml; charset=UTF-8";
            request.Method = "POST";
            request.ContentLength = payload.Length;
            request.Timeout = Timeout.Infinite;

            using (Stream dataStream = request.GetRequestStream())
            {

                dataStream.Write(payload, 0, payload.Length);
            }

            string s = request.Headers.ToString();
            WebResponse jobResponse = request.GetResponse();
            string responseText = GetResponseText(jobResponse);
            Regex idRegex = new Regex("<id>([^<]*)</id>");
            Match responseMatch = idRegex.Match(responseText);
            return responseMatch.Groups[1].Value;
        }

        /// <summary>
        /// Starts a Query Job (and gets the batchID)
        /// </summary>
        /// <param name="query">SOQL Query to execute</param>
        /// <param name="jobID">the Job_id</param>
        /// <returns>the BatchID</returns>
        internal static string BeginQuery(this ISalesForceConnection connection, string query, string jobID)
        {
            string url = String.Format("{0}job/{1}/batch", connection.GetUri_Base(), jobID);
            WebRequest request = WebRequest.Create(url);
            request.Headers.Add("X-SFDC-Session", connection.SFSession);

            request.ContentType = "text/csv; charset=UTF-8";
            request.Method = "POST";
            request.Timeout = Timeout.Infinite;
            System.Text.UTF8Encoding encoding = new UTF8Encoding();
            Byte[] payload = encoding.GetBytes(query);

            using (Stream dataStream = request.GetRequestStream())
            {

                dataStream.Write(payload, 0, payload.Length);
            }
            string s = request.Headers.ToString();

            WebResponse jobResponse = request.GetResponse();
            string responseText = GetResponseText(jobResponse);

            Regex idRegex = new Regex("<id>([^<]*)</id>");
            Match responseMatch = idRegex.Match(responseText);
            return responseMatch.Groups[1].Value;

        }
        
        /// <summary>
        /// Get the Results and Save them To dsik
        /// </summary>
        /// <param name="jobID">The Job ID</param>
        /// <param name="BatchID">BatchID</param>
        /// <param name="sleepTime">How long to sleep between Polling attempts</param>
        /// <param name="path">The Path to save the results</param>
        internal static void GetResults(this ISalesForceConnection connection, string jobID, string BatchID, int sleepTime, string path)
        {
            string url = String.Format("{0}job/{1}/batch/{2}", connection.GetUri_Base(), jobID, BatchID);

            bool notDone = true;

            while (notDone)
            {

                WebRequest request = WebRequest.Create(url);
                System.Net.Cache.RequestCachePolicy policy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                request.CachePolicy = policy;
                request.Headers.Add("X-SFDC-Session", connection.SFSession);
                request.Timeout = Timeout.Infinite;
                using (WebResponse jobResponse = request.GetResponse())
                {

                    string responseText = GetResponseText(jobResponse);

                    Regex stateRegex = new Regex("<state>([^<]*)</state>");
                    Match responseMatch = stateRegex.Match(responseText);
                    if (responseMatch.Groups[1].Value.Equals("Completed", StringComparison.OrdinalIgnoreCase))
                    {
                        notDone = false;
                    }
                    else if (responseMatch.Groups[1].Value.Equals("Failed", StringComparison.OrdinalIgnoreCase))
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine("Query Failed");
                        sb.AppendLine(responseText);
                    }
                    else
                    {
                        Thread.Sleep(sleepTime);
                    }
                }
            }

            string ResultRequestUri = url + "/result";
            WebRequest resultRequest = WebRequest.Create(ResultRequestUri);

            resultRequest.Headers.Add("X-SFDC-Session", connection.SFSession);
            WebResponse resultResponse = resultRequest.GetResponse();
            string resultText = GetResponseText(resultResponse);
            Regex resultRegex = new Regex("<result>([^<]*)</result>");
            MatchCollection resultCollection = resultRegex.Matches(resultText);
            int resultNum = 0;
            List<string> files = new List<string>();


            using (SmartThreadPool pool = new SmartThreadPool())
            {
                List<IWorkItemResult> waitForHandles = new List<IWorkItemResult>();
                foreach (Match resultMatch in resultCollection)
                {
                    resultNum++;
                    string resultId = resultMatch.Groups[1].Value;
                    string requestUri = String.Format("{0}/{1}", ResultRequestUri, resultId);

                    string TempfileName = Path.GetTempFileName();
                    files.Add(TempfileName);

                    IWorkItemResult result = pool.QueueWorkItem<ISalesForceConnection, string, string>(DownloadFile, connection, requestUri, TempfileName);
                    waitForHandles.Add(result);


                }
                pool.Start();
                SmartThreadPool.WaitAll(waitForHandles.ToArray());
                pool.Shutdown();
            }

            File.Move(files[0], path);

            if (files.Count > 1)
            {
                for (int i = 1; i < files.Count; i++)
                {
                    using (StreamReader reader = new StreamReader(files[i]))
                    {
                        string firstLine = reader.ReadLine(); //Throw away
                        File.AppendAllText(path, reader.ReadToEnd());
                    }
                    File.Delete(files[i]);
                }
            }

            return;
        }

        private static void DownloadFile(ISalesForceConnection connection, string requestUri, string TempfileName)
        {
            using (WebClient client = new WebClient())
            {
                client.Headers.Add("X-SFDC-Session", connection.SFSession);
                client.DownloadFile(requestUri, TempfileName);
            }
            
        }

        internal static void CloseJob(this ISalesForceConnection connection, string jobId)
        {
            string url = String.Format("{0}job/{1}", connection.GetUri_Base(), jobId);
            System.Text.UTF8Encoding encoding = new UTF8Encoding();
            Byte[] payload = encoding.GetBytes(Shutterstock.Salesforce.SSISDownload.Templates.Resources.closeJob);

            WebRequest request = WebRequest.Create(url);
            request.Headers.Add("X-SFDC-Session", connection.SFSession);

            request.ContentType = "application/xml; charset=UTF-8";
            request.Method = "POST";
            request.ContentLength = payload.Length;
            request.Timeout = Timeout.Infinite;
            using (Stream dataStream = request.GetRequestStream())
            {
                dataStream.Write(payload, 0, payload.Length);
            }

            WebResponse jobResponse = request.GetResponse();
            string responseText = GetResponseText(jobResponse);
            return;

        }

        internal static string GetResponseText(WebResponse jobResponse)
        {
            Stream responseStream = jobResponse.GetResponseStream();
            StreamReader sr = new StreamReader(responseStream);
            string responseText = sr.ReadToEnd();
            return responseText;
        }

    }
}
